Содержание

  • 1  Общая информация
  • 2  Предобработка данных
    • 2.1  Работа с пропусками
    • 2.2  Удаление дубликатов
  • 3  Добавление столбцов
  • 4  Анализ данных
    • 4.1  Общий анализ
    • 4.2  Исследование зависимости количества реакций от времени публикации поста
    • 4.3  Исследование зависимости скорости ответов на сообщения от времени создания треда
    • 4.4  Исследование зависимости количества реакций от типа группы и когорты
    • 4.5  Исследование зависимости количества реакций от типа канала
    • 4.6  Исследование количества реакиций в зависимости от длины сообщения
    • 4.7  Исследование количества реакций в канале info
  • 5  Общий вывод
  • 6  Рекомендации

Исследование активности в чатах Яндекс.Практикум¶

Заказчик исследования - Яндекс.Практикум. Выявленная проблема - низкая активность студентов в чатах, отсутствие реакции на важные анонсы. Для анализа предоставлен датасет с историей сообщений за определенный период. Необходимо:

  • проанализировать активность студентов в чатах, выявить паттерны, динамику, цикличность в течение дня, недели, месяца,
  • помочь понять, когда активность студентов в чатах наибольшая, и когда лучше публиковать посты/анонсы, чтобы получить больше откликов,
  • проанализировать различные типы каналов, когорт, групп,
  • оценить в каких общения больше, в каких меньше, и как они различаются от когорты к когорте, от канала к каналу.

Цель исследования: Выявить особенности активности студентов в чатах и определить, когда лучше публиковать посты, чтобы получить больше отклика.

Ход исследования: Данные представлены в таблице, предоставленной Яндекс.Практикумом. После изучения информации выполним поиск дубликатов и заполнение пропусков, если это возможно и необходимо. Добавим необходимые для анализа столбцы. Выполним расчеты путем создания группировок и покажем результаты на графиках. Сделаем выводы и представим их в конце.

Описание данных: Файл chat_data_clean.csv:

  • Unnamed: 0 индекс
  • client_msg_id id сообщения
  • type тип поста
  • user id пользователя
  • ts дата поста
  • thread_ts дата треда
  • latest_reply дата ответа
  • team факт вхождения в неизвестную группу
  • subtype метка действий пользователя
  • channel канал
  • file_date дата файла
  • attachments прикреплённые файлы
  • reactions реакции
  • text_len длина текста сообщения
  • text_words количество слов в сообщении

Общая информация¶

In [1]:
# импортируем библиотеки

import pandas as pd
import time
import re
import ast
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from plotly import graph_objects as go
from datetime import timedelta

import warnings
warnings.filterwarnings('ignore')

sns.set_style('whitegrid')
In [2]:
def custom_data_parser(cell):
    'дата парсер для загрузчика pandas.read_csv'
    try: 
        return pd.to_datetime(cell, unit='s').round('s') # обрабатываем ts, thread_ts, reply
    except:
        return pd.to_datetime(cell) # обрабатываем file_date
In [3]:
# загружаем данные

df = pd.read_csv('chat_data_clean.csv', index_col=0, 
                 parse_dates=['ts', 'thread_ts', 'latest_reply', 'file_date'], 
                 date_parser=custom_data_parser)
df.head()
Out[3]:
client_msg_id type user ts latest_reply team thread_ts subtype channel file_date attachments reactions text_len text_words
0 ae31e785-257b-4290-a4c6-9721337f67ea message U03JYMWQLP5 2022-11-28 13:49:23 2022-11-28 14:24:08 TPV9DP0N4 2022-11-28 13:49:23 NaN data-analysts-bus 2022-11-28 0 NaN 297 47
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d message U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data-analysts-bus 2022-11-28 0 [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... 434 63
2 NaN message U02KVQJHQ5S 2022-11-28 14:48:50 NaT NaN NaT channel_join data-analysts-bus 2022-11-28 0 NaN 37 5
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 message U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data-analysts-bus 2022-11-29 0 [{'name': 'cat-high-five', 'users': ['U040E2D6... 69 12
4 b5e3413b-8f04-4192-948b-2423eb3192b2 message U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data-analysts-bus 2022-11-29 0 NaN 19 2
In [4]:
# общая информация о датасете
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 26533 entries, 0 to 26532
Data columns (total 14 columns):
 #   Column         Non-Null Count  Dtype         
---  ------         --------------  -----         
 0   client_msg_id  18262 non-null  object        
 1   type           26533 non-null  object        
 2   user           23643 non-null  object        
 3   ts             26533 non-null  datetime64[ns]
 4   latest_reply   2303 non-null   datetime64[ns]
 5   team           15857 non-null  object        
 6   thread_ts      18222 non-null  datetime64[ns]
 7   subtype        8317 non-null   object        
 8   channel        26533 non-null  object        
 9   file_date      26533 non-null  datetime64[ns]
 10  attachments    26533 non-null  int64         
 11  reactions      4169 non-null   object        
 12  text_len       26533 non-null  int64         
 13  text_words     26533 non-null  int64         
dtypes: datetime64[ns](4), int64(3), object(7)
memory usage: 3.0+ MB

Датасет содержит информацию о 26533 сообщениях пользователей. Необходимо проанализировать пропуски и дубликаты при наличии.

Предобработка данных¶

Работа с пропусками¶

In [5]:
# проверим данные в столбце type на дубликаты
df['type'].value_counts()
Out[5]:
message    26533
Name: type, dtype: int64
In [6]:
# удаляем не несущий информации столбец 'type' - все сообщения имеют тип 'message'
df = df.drop('type', axis=1)
In [7]:
# проверим количество пропусков
pd.DataFrame(round(df.isna().mean()*100,2)).style.background_gradient('coolwarm')
Out[7]:
  0
client_msg_id 31.170000
user 10.890000
ts 0.000000
latest_reply 91.320000
team 40.240000
thread_ts 31.320000
subtype 68.650000
channel 0.000000
file_date 0.000000
attachments 0.000000
reactions 84.290000
text_len 0.000000
text_words 0.000000

Пропуски в столбце reactions - это отсутствие реакций, пропуски оставим как есть. Пропуски в latest_reply и thread_ts также означают отсутствие ответов и создания треда соответственно, оставим их как есть. Пропуски в столбце team оставим без изменений, так как нет данных, что обозначает этот столбец.

In [8]:
# проверим какие значения присутствуют в столбце 'subtype'
df['subtype'].value_counts()
Out[8]:
channel_leave        3265
bot_message          2890
channel_join         2026
thread_broadcast      108
tombstone              14
channel_unarchive      10
channel_purpose         2
bot_remove              1
channel_name            1
Name: subtype, dtype: int64

Значения столбца subtype - это служебные сообщения и отметки. Заполнять пропуски нет необходимости, нужно считать что сообщения без этих отметок, скорее всего, в большинстве своем от реальных людей.

In [9]:
# проверим зависимость отсутствия id сообщения и служебных меток
df1 = df[df['client_msg_id'].isnull()].groupby('subtype')[['ts']].count()
df1
Out[9]:
ts
subtype
bot_message 2890
bot_remove 1
channel_join 2026
channel_leave 3265
channel_name 1
channel_purpose 2
channel_unarchive 10
tombstone 14

Большинство пропущенных значений client_id_msg - это сообщения со служебными метками.

In [10]:
# проверим сколько сообщений не имеют id и не имеют служебных меток
df[(df['client_msg_id'].isnull())&(df['subtype'].isnull())]
Out[10]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments reactions text_len text_words
197 NaN U01QMDLG9AP 2022-12-13 14:06:04 2022-12-13 14:19:01 TPV9DP0N4 2022-12-13 14:06:04 NaN data_edteam_info 2022-12-13 1 NaN 208 30
2064 NaN U0203TTA1AQ 2022-11-27 16:19:18 2022-12-11 08:57:06 TPV9DP0N4 2022-11-27 16:19:18 NaN da_55_projects 2022-11-27 0 NaN 227 36
2065 NaN U0203TTA1AQ 2022-11-27 16:19:18 NaT TPV9DP0N4 NaT NaN da_55_projects 2022-11-27 0 NaN 182 30
2066 NaN U0203TTA1AQ 2022-11-27 16:19:19 2022-12-07 20:01:34 TPV9DP0N4 2022-11-27 16:19:19 NaN da_55_projects 2022-11-27 0 NaN 522 82
2067 NaN U0203TTA1AQ 2022-11-27 16:19:19 2022-12-05 15:29:32 TPV9DP0N4 2022-11-27 16:19:19 NaN da_55_projects 2022-11-27 0 NaN 207 33
... ... ... ... ... ... ... ... ... ... ... ... ... ...
18174 NaN U0203TTA1AQ 2022-11-27 15:58:50 NaT TPV9DP0N4 NaT NaN ds_49_projects 2022-11-27 0 NaN 255 32
18175 NaN U0203TTA1AQ 2022-11-27 15:58:50 2022-12-10 20:28:09 TPV9DP0N4 2022-11-27 15:58:50 NaN ds_49_projects 2022-11-27 0 NaN 66 9
23639 NaN U04ABUPLS83 2022-12-12 09:31:44 NaT TPV9DP0N4 NaT NaN ds_58_teamwork 2022-12-12 1 NaN 14 1
25971 NaN U0431FHJFV5 2022-12-01 14:33:18 NaT TPV9DP0N4 NaT NaN ds_plus_18_teamwork 2022-12-01 1 NaN 0 0
26452 NaN U02JSUUMARF 2022-12-14 11:17:54 NaT TPV9DP0N4 NaT NaN sql_info 2022-12-14 1 NaN 281 34

62 rows × 13 columns

Сообщений без служебных меток и без номера сообщения 62 строки. Какой-то закономерности не прослеживается, скорее всего это ошибка выгрузки.

In [11]:
# проверим какие сообщения со служебными метками имеют id
df.groupby('subtype')[['client_msg_id']].count()
Out[11]:
client_msg_id
subtype
bot_message 0
bot_remove 0
channel_join 0
channel_leave 0
channel_name 0
channel_purpose 0
channel_unarchive 0
thread_broadcast 108
tombstone 0

Только одной категории служебных сообщений присвоены номера сообщений - thread_broadcast. Пропуски в id сообщений оставим без изменений.

In [12]:
# проверим пропуски в столбце 'user'
df[df['user'].isnull()].head()
Out[12]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments reactions text_len text_words
520 NaN NaN 2022-11-28 07:00:57 NaT NaN NaT bot_message da_53_exerciser_1 2022-11-27 0 NaN 76 12
521 NaN NaN 2022-11-28 07:00:57 2022-11-28 12:34:02 NaN 2022-11-28 07:00:57 bot_message da_53_exerciser_1 2022-11-27 0 NaN 93 15
522 NaN NaN 2022-11-28 07:00:58 2022-12-08 14:12:27 NaN 2022-11-28 07:00:58 bot_message da_53_exerciser_1 2022-11-27 0 NaN 80 13
523 NaN NaN 2022-11-28 07:00:58 2022-12-07 14:14:53 NaN 2022-11-28 07:00:58 bot_message da_53_exerciser_1 2022-11-27 0 NaN 138 18
524 NaN NaN 2022-11-28 07:00:58 NaT NaN NaT bot_message da_53_exerciser_1 2022-11-27 0 NaN 121 18
In [13]:
# проверим гипотезу: сообщения со служебной меткой 'bot_message' не имеют user id
df[df['user'].isnull()].groupby('subtype')['ts'].count()
Out[13]:
subtype
bot_message    2890
Name: ts, dtype: int64

Действительно, сообщения от бота не имеют user id. Пропуски в столбце userзаполнять не будем, эта информация нам не понадобится в исследовании.

Удаление дубликатов¶

In [14]:
# проверим наличие явных дубликатов
df.duplicated().sum()
Out[14]:
369
In [15]:
# удаляем явные дубликаты
df = df.drop_duplicates()
df.duplicated().sum()
Out[15]:
0
In [16]:
# рассмотрим данные в столбце channel - неявных дубликатов нет
set(df.channel)
Out[16]:
{'da_42_exerciser_1',
 'da_42_exerciser_2',
 'da_42_projects_1',
 'da_42_projects_2',
 'da_50_info',
 'da_50_library',
 'da_50_teamwork',
 'da_52_exerciser',
 'da_52_info',
 'da_52_library',
 'da_52_projects',
 'da_52_teamwork',
 'da_53_exerciser_1',
 'da_53_exerciser_2',
 'da_53_info',
 'da_53_library',
 'da_53_projects_1',
 'da_53_projects_2',
 'da_53_teamwork',
 'da_54_exerciser_01',
 'da_54_exerciser_02',
 'da_54_info',
 'da_54_library',
 'da_54_projects_01',
 'da_54_projects_02',
 'da_54_teamwork',
 'da_55_exerciser',
 'da_55_info',
 'da_55_library',
 'da_55_projects',
 'da_55_teamwork',
 'da_56_exerciser_1',
 'da_56_exerciser_2',
 'da_56_info',
 'da_56_library',
 'da_56_projects_1',
 'da_56_projects_2',
 'da_56_teamwork',
 'da_56b_exerciser',
 'da_56b_info',
 'da_56b_library',
 'da_56b_projects',
 'da_56b_teamwork',
 'da_58_digitalprof',
 'da_58_exerciser_1',
 'da_58_exerciser_2',
 'da_58_exerciser_3',
 'da_58_info',
 'da_58_library',
 'da_58_projects_1',
 'da_58_projects_2',
 'da_58_projects_3',
 'da_58_teamwork',
 'da_59_exerciser_01',
 'da_59_exerciser_02',
 'da_59_exerciser_03',
 'da_59_info',
 'da_59_library',
 'da_59_projects_01',
 'da_59_projects_02',
 'da_59_projects_03',
 'da_59_teamwork',
 'da_59b_exerciser',
 'da_59b_info',
 'da_59b_library',
 'da_59b_projects',
 'da_59b_teamwork',
 'da_60_exerciser_01',
 'da_60_exerciser_02',
 'da_60_exerciser_03',
 'da_60_info',
 'da_60_library',
 'da_60_projects_01',
 'da_60_projects_02',
 'da_60_projects_03',
 'da_60_teamwork',
 'da_60_цифровые-профессии',
 'da_61_exerciser_01',
 'da_61_exerciser_02',
 'da_61_exerciser_03',
 'da_61_info',
 'da_61_library',
 'da_61_project_01',
 'da_61_project_02',
 'da_61_project_03',
 'da_61_teamwork',
 'da_61b_exerciser',
 'da_61b_info',
 'da_61b_projects',
 'da_61b_teamwork',
 'da_62_b2g',
 'da_62_exerciser_1',
 'da_62_exerciser_2',
 'da_62_info',
 'da_62_library',
 'da_62_projects_1',
 'da_62_projects_2',
 'da_62_teamwork',
 'da_63_exerciser_1',
 'da_63_exerciser_2',
 'da_63_info',
 'da_63_library',
 'da_63_mentors',
 'da_63_projects_1',
 'da_63_projects_2',
 'da_63_teamwork',
 'da_63_цифровые_профессии',
 'da_bc_02_apps',
 'da_bc_02_final_info',
 'da_bc_02_telecom',
 'da_bc_03_info',
 'da_bc_03_library',
 'da_bc_03_study',
 'da_bc_03_teamwork',
 'da_bc_04_info',
 'da_bc_04_library',
 'da_bc_04_study',
 'da_bc_04_teamwork',
 'da_plus_09_exerciser',
 'da_plus_09_info',
 'da_plus_09_kt',
 'da_plus_09_library',
 'da_plus_09_projects',
 'da_plus_09_teamwork',
 'da_plus_10_exerciser',
 'da_plus_10_info',
 'da_plus_10_kt',
 'da_plus_10_library',
 'da_plus_10_mentors',
 'da_plus_10_projects',
 'da_plus_10_teamwork',
 'da_plus_11_exerciser',
 'da_plus_11_info',
 'da_plus_11_library',
 'da_plus_11_project',
 'da_plus_11_teamwork',
 'da_plus_12_exerciser',
 'da_plus_12_info',
 'da_plus_12_kt',
 'da_plus_12_library',
 'da_plus_12_projects',
 'da_plus_12_teamwork',
 'da_plus_13_exerciser',
 'da_plus_13_info',
 'da_plus_13_library',
 'da_plus_13_projects',
 'da_plus_13_teamwork',
 'da_plus_14_exerciser',
 'da_plus_14_info',
 'da_plus_14_library',
 'da_plus_14_projects',
 'da_plus_14_teamwork',
 'da_plus_15_exerciser',
 'da_plus_15_info',
 'da_plus_15_library',
 'da_plus_15_projects',
 'da_plus_15_teamwork',
 'da_plus_16_exerciser',
 'da_plus_16_info',
 'da_plus_16_library',
 'da_plus_16_mentors',
 'da_plus_16_projects',
 'da_plus_16_teamwork',
 'da_plus_17_exerciser',
 'da_plus_17_info',
 'da_plus_17_library',
 'da_plus_17_projects',
 'da_plus_17_teamwork',
 'da_plus_18_exerciser_1',
 'da_plus_18_exerciser_2',
 'da_plus_18_info',
 'da_plus_18_library',
 'da_plus_18_projects_1',
 'da_plus_18_projects_2',
 'da_plus_18_teamwork',
 'da_plus_19_exerciser',
 'da_plus_19_info',
 'da_plus_19_library',
 'da_plus_19_mentors',
 'da_plus_19_projects',
 'da_plus_19_teamwork',
 'da_plus_8_info',
 'da_plus_8_kt',
 'da_plus_8_library',
 'da_plus_8_teamwork',
 'data-analysts-bus',
 'data_complaints',
 'data_edteam_info',
 'datatracker_logs',
 'de-pro1',
 'de-pro2',
 'de-pro3',
 'de-pro4',
 'de-project',
 'de_1_info',
 'de_22_community',
 'de_22_exerciser',
 'de_22_group_orange',
 'de_22_group_red',
 'de_22_group_yellow',
 'de_22_info',
 'de_22_library',
 'de_22_projects',
 'de_22_tutorial',
 'de_23_community',
 'de_23_exerciser',
 'de_23_group_blue',
 'de_23_group_green',
 'de_23_group_red',
 'de_23_group_yellow',
 'de_23_info',
 'de_23_library',
 'de_23_problem_1',
 'de_23_projects',
 'de_23_tutorial',
 'de_24_community',
 'de_24_exerciser',
 'de_24_group_green',
 'de_24_group_orange',
 'de_24_group_red',
 'de_24_group_yellow',
 'de_24_info',
 'de_24_library',
 'de_24_projects',
 'de_24_topic1',
 'de_24_topic2',
 'de_24_topic3',
 'de_24_tutorial',
 'de_25_community',
 'de_25_exerciser',
 'de_25_group_blue',
 'de_25_group_orange',
 'de_25_group_purple',
 'de_25_group_red',
 'de_25_info',
 'de_25_library',
 'de_25_projects',
 'de_25_tutorial',
 'de_26_community',
 'de_26_exerciser',
 'de_26_group_green',
 'de_26_group_purple',
 'de_26_group_red',
 'de_26_group_yellow',
 'de_26_info',
 'de_26_library',
 'de_26_projects',
 'de_26_tutorial',
 'de_27_2_community',
 'de_27_2_digital_professions',
 'de_27_2_exerciser',
 'de_27_2_group_green',
 'de_27_2_group_orange',
 'de_27_2_group_yellow',
 'de_27_2_info',
 'de_27_2_library',
 'de_27_2_projects',
 'de_27_2_tutorial',
 'de_27_community',
 'de_27_exerciser',
 'de_27_group_green',
 'de_27_group_red',
 'de_27_group_yellow',
 'de_27_info',
 'de_27_library',
 'de_27_projects',
 'de_27_tutorial',
 'de_28_2_community',
 'de_28_2_digital_professions',
 'de_28_2_exerciser',
 'de_28_2_group_green',
 'de_28_2_group_red',
 'de_28_2_group_yellow',
 'de_28_2_info',
 'de_28_2_library',
 'de_28_2_projects',
 'de_28_2_tutorial',
 'de_28_community',
 'de_28_digital_professions',
 'de_28_exerciser',
 'de_28_group_green',
 'de_28_group_red',
 'de_28_group_yellow',
 'de_28_info',
 'de_28_library',
 'de_28_projects',
 'de_28_tutorial',
 'de_29_2_community',
 'de_29_2_digital_professions',
 'de_29_2_exerciser',
 'de_29_2_group_green',
 'de_29_2_group_red',
 'de_29_2_group_yellow',
 'de_29_2_info',
 'de_29_2_library',
 'de_29_2_mentors',
 'de_29_2_projects',
 'de_29_2_tutorial',
 'de_29_community',
 'de_29_digital_professions',
 'de_29_exerciser',
 'de_29_group_blue',
 'de_29_group_purple',
 'de_29_group_red',
 'de_29_info',
 'de_29_library',
 'de_29_projects',
 'de_29_tutorial',
 'de_2_info',
 'de_30_community',
 'de_30_digital_professions',
 'de_30_exerciser',
 'de_30_group_green',
 'de_30_group_red',
 'de_30_group_yellow',
 'de_30_info',
 'de_30_library',
 'de_30_mentors',
 'de_30_projects',
 'de_30_tutorial',
 'de_3_exerciser',
 'de_3_info',
 'de_3_projects',
 'de_4_exerciser',
 'de_4_info',
 'de_4_projects',
 'de_5_exerciser',
 'de_5_info',
 'de_5_library',
 'de_5_mentors',
 'de_5_projects',
 'de_5_teamwork',
 'de_6_exerciser',
 'de_6_info',
 'de_6_library',
 'de_6_mentors',
 'de_6_projects',
 'de_6_teamwork',
 'de_7_exerciser',
 'de_7_info',
 'de_7_library',
 'de_7_mentors',
 'de_7_projects',
 'de_7_teamwork',
 'de_8_exerciser',
 'de_8_info',
 'de_8_library',
 'de_8_mentors',
 'de_8_projects',
 'de_8_teamwork',
 'de_8_workshop_project1',
 'de_random_coffee',
 'design_web_plus',
 'dl_04_community',
 'dl_04_info',
 'dl_04_students_feedback',
 'dl_04_teach_me',
 'dl_05_community',
 'dl_05_info',
 'dl_05_students_feedback',
 'dl_05_teach_me',
 'dl_06_community',
 'dl_06_group_green',
 'dl_06_group_yellow',
 'dl_06_info',
 'dl_06_student_feedback',
 'dl_06_teach_me',
 'dl_07_community',
 'dl_07_info',
 'dl_07_student_feedback',
 'dl_07_teach_me',
 'dl_academ',
 'donorsearch',
 'ds_35_exerciser_1',
 'ds_35_exerciser_2',
 'ds_35_exerciser_3',
 'ds_35_projects_1',
 'ds_35_projects_2',
 'ds_35_projects_3',
 'ds_38_info',
 'ds_38_library',
 'ds_38_teamwork',
 'ds_40_info',
 'ds_40_library',
 'ds_40_teamwork',
 'ds_42_exerciser',
 'ds_42_info',
 'ds_42_library',
 'ds_42_project',
 'ds_42_teamwork',
 'ds_43_info',
 'ds_43_library',
 'ds_43_teamwork',
 'ds_44_exerciser',
 'ds_44_info',
 'ds_44_library',
 'ds_44_projects',
 'ds_44_teamwork',
 'ds_45_exerciser',
 'ds_45_info',
 'ds_45_library',
 'ds_45_projects',
 'ds_45_teamwork',
 'ds_46_exerciser',
 'ds_46_info',
 'ds_46_library',
 'ds_46_projects',
 'ds_46_teamwork',
 'ds_47_exerciser',
 'ds_47_info',
 'ds_47_library',
 'ds_47_projects',
 'ds_47_teamwork',
 'ds_48_exerciser',
 'ds_48_info',
 'ds_48_library',
 'ds_48_projects',
 'ds_48_teamwork',
 'ds_49_exerciser',
 'ds_49_info',
 'ds_49_library',
 'ds_49_projects',
 'ds_49_teamwork',
 'ds_50_exerciser_1',
 'ds_50_exerciser_2',
 'ds_50_info',
 'ds_50_library',
 'ds_50_projects_1',
 'ds_50_projects_2',
 'ds_50_teamwork',
 'ds_51b_info',
 'ds_51b_library',
 'ds_51b_projects',
 'ds_51b_teamwork',
 'ds_54b_exerciser',
 'ds_54b_info',
 'ds_54b_library',
 'ds_54b_projects',
 'ds_54b_teamwork',
 'ds_55_exerciser_1',
 'ds_55_exerciser_2',
 'ds_55_exerciser_3',
 'ds_55_info',
 'ds_55_library',
 'ds_55_projects_1',
 'ds_55_projects_2',
 'ds_55_teamwork',
 'ds_57_exerciser_1',
 'ds_57_exerciser_2',
 'ds_57_info',
 'ds_57_library',
 'ds_57_projects_1',
 'ds_57_projects_2',
 'ds_57_teamwork',
 'ds_58_exerciser_1',
 'ds_58_exerciser_2',
 'ds_58_info',
 'ds_58_library',
 'ds_58_projects_1',
 'ds_58_projects_2',
 'ds_58_teamwork',
 'ds_bc_02_exerciser',
 'ds_bc_02_info',
 'ds_bc_02_library',
 'ds_bc_02_projects',
 'ds_bc_02_teamwork',
 'ds_bc_03_exerciser',
 'ds_bc_03_info',
 'ds_bc_03_library',
 'ds_bc_03_projects',
 'ds_bc_03_teamwork',
 'ds_bc_04_exerciser',
 'ds_bc_04_info',
 'ds_bc_04_library',
 'ds_bc_04_projects',
 'ds_bc_04_teamwork',
 'ds_bc_05_info',
 'ds_bc_05_library',
 'ds_bc_05_study',
 'ds_bc_05_teamwork',
 'ds_plus_05_exerciser',
 'ds_plus_05_info',
 'ds_plus_05_projects',
 'ds_plus_05_teamwork',
 'ds_plus_06_exerciser',
 'ds_plus_06_info',
 'ds_plus_06_projects',
 'ds_plus_06_teamwork',
 'ds_plus_07_exerciser',
 'ds_plus_07_info',
 'ds_plus_07_projects',
 'ds_plus_07_teamwork',
 'ds_plus_08_exerciser',
 'ds_plus_08_info',
 'ds_plus_08_library',
 'ds_plus_08_projects',
 'ds_plus_08_teamwork',
 'ds_plus_09_exerciser',
 'ds_plus_09_info',
 'ds_plus_09_kt',
 'ds_plus_09_library',
 'ds_plus_09_projects',
 'ds_plus_09_teamwork',
 'ds_plus_10_exerciser',
 'ds_plus_10_info',
 'ds_plus_10_library',
 'ds_plus_10_projects',
 'ds_plus_10_teamwork',
 'ds_plus_11_exerciser',
 'ds_plus_11_library',
 'ds_plus_11_projects',
 'ds_plus_11_teamwork',
 'ds_plus_12_exerciser',
 'ds_plus_12_info',
 'ds_plus_12_library',
 'ds_plus_12_projects',
 'ds_plus_12_teamwork',
 'ds_plus_13_exerciser',
 'ds_plus_13_info',
 'ds_plus_13_library',
 'ds_plus_13_projects',
 'ds_plus_13_teamwork',
 'ds_plus_14_exerciser',
 'ds_plus_14_info',
 'ds_plus_14_library',
 'ds_plus_14_projects',
 'ds_plus_14_teamwork',
 'ds_plus_15_exerciser',
 'ds_plus_15_info',
 'ds_plus_15_library',
 'ds_plus_15_projects_1',
 'ds_plus_15_projects_2',
 'ds_plus_15_teamwork',
 'ds_plus_17_exerciser',
 'ds_plus_17_info',
 'ds_plus_17_library',
 'ds_plus_17_mentors',
 'ds_plus_17_projects',
 'ds_plus_17_teamwork',
 'ds_plus_18_exerciser',
 'ds_plus_18_info',
 'ds_plus_18_library',
 'ds_plus_18_projects',
 'ds_plus_18_teamwork',
 'ds_plus_19_exerciser',
 'ds_plus_19_info',
 'ds_plus_19_library',
 'ds_plus_19_projects',
 'ds_plus_19_teamwork',
 'masterskaya_10ds_plus',
 'masterskaya_10dа_plus',
 'masterskaya_11ds_plus',
 'masterskaya_11dа_plus',
 'masterskaya_12ds_plus',
 'masterskaya_7ds_plus',
 'masterskaya_8ds_plus',
 'masterskaya_8dа_plus',
 'masterskaya_9ds_plus',
 'masterskaya_9dа_plus',
 'sql_04_exerciser',
 'sql_04_info',
 'sql_04_teamwork',
 'sql_exerciser',
 'sql_exerciser_03',
 'sql_exerciser_new',
 'sql_info',
 'sql_info_03',
 'sql_info_new',
 'sql_teamwork',
 'sql_teamwork_03',
 'sql_teamwork_new'}
In [17]:
# проверим наличие дубликатов в столбце subtype - неявных дубликатов нет
df['subtype'].value_counts()
Out[17]:
channel_leave        3265
bot_message          2521
channel_join         2026
thread_broadcast      108
tombstone              14
channel_unarchive      10
channel_purpose         2
bot_remove              1
channel_name            1
Name: subtype, dtype: int64
In [18]:
# рассмотрим распределение и выбросы в столбце text_len

plt.figure(figsize=(15, 5))

sns.boxplot(data=df, x='text_len', color='red')
plt.xlim(0,1000);
In [19]:
# рассмотрим распределение и выбросы в столбце text_words
plt.figure(figsize=(15, 5))

sns.boxplot(data=df, x='text_words', color='red')
plt.xlim(0,200);

Сообщения больше 400 символов и больше 55 слов отмечены как выбросы. Таких сообщений меньше, чем коротких, но это не говорит об аномалии. Многие обяснения могут быть написаны длинными сообщениями. Поэтому оставим этот столбец без изменений.

В результате предобработки мы удалили не несущий информацию столбец type. Выяснили, что большинство пропущенных значений client_id_msg - это сообщения со служебными метками, а также что сообщения от бота не имеют user id. Пропуски в столбцах subtype, client_msg_id, latest_reply, tread_ts, team, user оставили как есть. Удалили явные дубликаты, а также выяснили, что большинство сообщений находятся в пределах 55 слов и 400 символов.

Добавление столбцов¶

In [20]:
# создадим функцию для выделения типа канала 

def channel_type(cell):
    'определение канала в столбце channel'
    
    channels = ['info', 'exerciser', 'teamwork', 'projects', 'library', 'masterskaya']
    
    for channel in channels:
        if channel in cell:
            return channel
    return 'other'
In [21]:
# создадим столбец с типом канала используя написанную функцию

df['channel_type'] = df['channel'].apply(channel_type)
df.head()
Out[21]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments reactions text_len text_words channel_type
0 ae31e785-257b-4290-a4c6-9721337f67ea U03JYMWQLP5 2022-11-28 13:49:23 2022-11-28 14:24:08 TPV9DP0N4 2022-11-28 13:49:23 NaN data-analysts-bus 2022-11-28 0 NaN 297 47 other
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data-analysts-bus 2022-11-28 0 [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... 434 63 other
2 NaN U02KVQJHQ5S 2022-11-28 14:48:50 NaT NaN NaT channel_join data-analysts-bus 2022-11-28 0 NaN 37 5 other
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data-analysts-bus 2022-11-29 0 [{'name': 'cat-high-five', 'users': ['U040E2D6... 69 12 other
4 b5e3413b-8f04-4192-948b-2423eb3192b2 U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data-analysts-bus 2022-11-29 0 NaN 19 2 other
In [22]:
# приведем к единообразию символы в столбце channel

df['channel'] = df['channel'].str.replace('-', '_')
In [23]:
# создадим столбец с выделенным типом группы, используя регулярное выражение

df['group_type'] = df['channel'].str.extract(r'\b(data|da_plus|da_bc|da|de|dl|ds_plus|ds_bc|ds|sql|masterskaya)_')

# заменим пропуски в новом столбце на 'other'

df['group_type'] = df['group_type'].fillna('other')
In [24]:
# проверим результат

df['group_type'].value_counts()
Out[24]:
da             10320
ds              7156
de              1981
da_plus         1946
ds_plus         1808
dl              1325
ds_bc            672
da_bc            468
sql              207
data             206
masterskaya       71
other              4
Name: group_type, dtype: int64
In [25]:
# выделим номер когорты с помощью регулярного выражения

df['cohort'] = df['channel'].str.extract(r'_(\d+|\d+b)_')

# заполним пропуски

df['cohort'] = df['cohort'].fillna('0')
In [26]:
# создадим столбец с группой и номером когорты

df['group_cohort'] = df['group_type']+('_')+df['cohort']
In [27]:
# заполняем пропуски в столбце reactions

df['reactions'] = df['reactions'].fillna(0)
In [28]:
# пишем функцию для подсчета суммы реакций

def count_of_reactions(row):
    if row != 0:
        row = ast.literal_eval(row)
        return sum(item['count'] for item in row)
    else:
        pass
In [29]:
# применим функцию для создания нового столбца. Преобразуем данные в тип int и заполним пропуски 0

df['count_of_reactions'] = df['reactions'].apply(count_of_reactions).fillna(0).astype('int')
df.head()
Out[29]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments reactions text_len text_words channel_type group_type cohort group_cohort count_of_reactions
0 ae31e785-257b-4290-a4c6-9721337f67ea U03JYMWQLP5 2022-11-28 13:49:23 2022-11-28 14:24:08 TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 0 297 47 other data 0 data_0 0
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 [{'name': 'pray', 'users': ['U03JYMWQLP5'], 'c... 434 63 other data 0 data_0 1
2 NaN U02KVQJHQ5S 2022-11-28 14:48:50 NaT NaN NaT channel_join data_analysts_bus 2022-11-28 0 0 37 5 other data 0 data_0 0
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 [{'name': 'cat-high-five', 'users': ['U040E2D6... 69 12 other data 0 data_0 1
4 b5e3413b-8f04-4192-948b-2423eb3192b2 U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 0 19 2 other data 0 data_0 0
In [30]:
# выделим время создания, день недели, день месяца, месяц сообщения и дату

df['msg_hour'] = pd.DatetimeIndex(df['ts']).hour
df['msg_weekday'] = pd.DatetimeIndex(df['ts']).weekday
df['msg_day'] = pd.DatetimeIndex(df['ts']).day
df['msg_month'] = pd.DatetimeIndex(df['ts']).month
df['msg_date'] = pd.DatetimeIndex(df['ts']).date

df.head()
Out[30]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments ... channel_type group_type cohort group_cohort count_of_reactions msg_hour msg_weekday msg_day msg_month msg_date
0 ae31e785-257b-4290-a4c6-9721337f67ea U03JYMWQLP5 2022-11-28 13:49:23 2022-11-28 14:24:08 TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 ... other data 0 data_0 0 13 0 28 11 2022-11-28
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 ... other data 0 data_0 1 14 0 28 11 2022-11-28
2 NaN U02KVQJHQ5S 2022-11-28 14:48:50 NaT NaN NaT channel_join data_analysts_bus 2022-11-28 0 ... other data 0 data_0 0 14 0 28 11 2022-11-28
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... other data 0 data_0 1 8 1 29 11 2022-11-29
4 b5e3413b-8f04-4192-948b-2423eb3192b2 U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... other data 0 data_0 0 8 1 29 11 2022-11-29

5 rows × 23 columns

Так как сообщения со служебными метками в основном сообщают о том, что кто-то покинул или вступил в канал, логично предположить, что на эти сообщения реакций не ждут и лучше исключить их из анализа воизбежание искажения результата.

In [31]:
# проверим, получают ли реакции служебные сообщения

df[(df['subtype'].notnull())&(df['count_of_reactions']>0)].groupby('subtype')['user'].count()
Out[31]:
subtype
bot_message          0
channel_join         1
channel_leave        2
thread_broadcast    37
tombstone            2
Name: user, dtype: int64

Cообщения, имеющие метку thread_broadcast, получают реакции и имеют client_msg_id. Метка говорит о том, что сообщенние транслируется на весь канал. Оставим эти сообщения, остальные служебные сообщения удалим.

In [32]:
df = df[(df['subtype'].isnull())|(df['subtype'] == 'thread_broadcast')]
In [33]:
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 18324 entries, 0 to 26530
Data columns (total 23 columns):
 #   Column              Non-Null Count  Dtype         
---  ------              --------------  -----         
 0   client_msg_id       18262 non-null  object        
 1   user                18324 non-null  object        
 2   ts                  18324 non-null  datetime64[ns]
 3   latest_reply        1376 non-null   datetime64[ns]
 4   team                15765 non-null  object        
 5   thread_ts           17289 non-null  datetime64[ns]
 6   subtype             108 non-null    object        
 7   channel             18324 non-null  object        
 8   file_date           18324 non-null  datetime64[ns]
 9   attachments         18324 non-null  int64         
 10  reactions           18324 non-null  object        
 11  text_len            18324 non-null  int64         
 12  text_words          18324 non-null  int64         
 13  channel_type        18324 non-null  object        
 14  group_type          18324 non-null  object        
 15  cohort              18324 non-null  object        
 16  group_cohort        18324 non-null  object        
 17  count_of_reactions  18324 non-null  int32         
 18  msg_hour            18324 non-null  int64         
 19  msg_weekday         18324 non-null  int64         
 20  msg_day             18324 non-null  int64         
 21  msg_month           18324 non-null  int64         
 22  msg_date            18324 non-null  object        
dtypes: datetime64[ns](4), int32(1), int64(7), object(11)
memory usage: 3.3+ MB

Мы добавили в таблицу следующие столбцы: channel_type - тип канала, group_type - тип группы, cohort - когорта, group_cohort - группа и когорта, count_of_reactions - количество реакций на пост, msg_hour - время сообщения, msg_weekday - день недели сообщения, msg_day - день месяца сообщения, msg_month - месяц сообщения, msg_date - дату сообщения. Также из таблицы были удалены служебные сообщения, не несущие информации для анализа.

Анализ данных¶

Общий анализ¶

In [34]:
# посморим диапазон дат представленных сообщений

min_ts = df['ts'].min()
max_ts = df['ts'].max()

print('Дата публикации первого сообщения ', min_ts,)
print('Дата публикации последнего сообщения ', max_ts)
Дата публикации первого сообщения  2022-10-21 09:00:00
Дата публикации последнего сообщения  2022-12-25 11:33:22

Для анализа предоставлены данные чуть больше чем за 2 месяца - с 21.10.22 по 25.12.22 гг.

In [35]:
# распределение по месяцам

count_of_msg_month = df.groupby('msg_month')[['user']].count()
count_of_msg_month
Out[35]:
user
msg_month
10 3
11 4256
12 14065
In [36]:
plt.figure(figsize=(14, 3))
df.groupby('ts').agg({'ts':'count'}).plot(kind='line')
plt.show()
<Figure size 1008x216 with 0 Axes>

Большинство сообщений опубликовано в декабре.

In [37]:
# распределение количества сообщений по группам

plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
    ax = sns.countplot(data=df, x='group_type', order=df['group_type'].value_counts().index)
    ax.set_title('Количество сообщений в разных типах групп')
    ax.set_xlabel('Тип группы')
    ax.set_ylabel('Количество')
    sns.despine();

Больше всего сообщений генерируют группы da, что логично - их больше всего когорт.

In [38]:
# распределение количества сообщений по каналам

plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
    ax = sns.countplot(data=df, x='channel_type', order=df['channel_type'].value_counts().index)
    ax.set_title('Количество сообщений в разных типах каналов')
    ax.set_xlabel('Тип канала')
    ax.set_ylabel('Количество')
    plt.xticks (rotation = 30)
    sns.despine();

Больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и как следствие большим количеством вопросов.

In [39]:
# длина сообщений в каналах

fig = px.box(df,
              x='channel_type', 
              y='text_len', 
              color='channel_type', 
            notched=True)  # вырез у медианы
fig.update_layout(title='Медиана длины сообщений по типам каналов',
                   xaxis_title='Тип канала',
                   yaxis_title='Количество символов', yaxis_range=[0, 400],
                   legend_title_text='Тип канала')
fig.update_xaxes(categoryorder='array', categoryarray=\
                 df.groupby('channel_type')['text_len'].median().sort_values(ascending=False).index)

fig.show()

Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.

In [40]:
# длина сообщений по типам групп

fig = px.box(df,
              x='group_type', 
              y='text_len', 
              color='group_type')
fig.update_layout(title='Медиана длины сообщений по типам групп',
                   xaxis_title='Тип группы',
                   yaxis_title='Количество символов', yaxis_range=[0, 400],
                   legend_title_text='Тип группы')
fig.update_xaxes(categoryorder='array', categoryarray=\
                 df.groupby('group_type')['text_len'].median().sort_values(ascending=False).index)
fig.show()

Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.

In [41]:
# добавим столбец, указывающий что пост имеет хотя бы  реакцию
# напишем функцию для метки столбца 

def reaction(number):
    if number > 0:
        return 'пост с реакцией'
    return 'пост без реакции'
In [42]:
df['is_reaction'] = df['count_of_reactions'].apply(reaction)
In [43]:
# проведем группировку для графика

react = df.groupby('is_reaction')[['user']].count()
react
Out[43]:
user
is_reaction
пост без реакции 14219
пост с реакцией 4105
In [44]:
# круговая диаграмма доли постов с наличием хотя бы  реакции

fig = px.pie(react, values='user', names=react.index, color_discrete_sequence=px.colors.sequential.Burgyl, \
             title='Доля постов с реакциями', hole=.6)
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.update_layout(showlegend=False)
fig.show()

Как видно из диаграммы, только 22,4% постов получают хотя бы одну реакцию, что довольно немного.

In [45]:
# рассмотрим общее распределение сообщений в течение дня

count_of_msg = df.groupby('msg_hour')[['user']].count()

plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg.index, y='user', data=count_of_msg, marker='D', color='red')

plt.title('Распределение выхода сообщений по времени дня')
plt.xlabel('Время дня')
plt.ylabel('Количество сообщений')
# украшения ревьюера
plt.axvline(x=7, linestyle='--', linewidth=2, color='green')
plt.axvline(x=18, linestyle='--', linewidth=2, color='green')
plt.annotate('Максимальная активность', xy=(11, 1000))
import numpy as np
plt.xticks(np.arange(0, 23, step=1)) # шкалу часов немного поменял внизу
plt.show()

Самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше.

In [46]:
# рассмотрим общее распределение сообщений в течение недели

count_of_msg_week = df.groupby('msg_weekday')[['user']].count()

plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg_week.index, y='user', data=count_of_msg_week, marker='D', color='red')

plt.title('Распределение выхода сообщений в течение недели')
plt.xlabel('День недели')
plt.ylabel('Количество сообщений')

plt.show()

Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу.

In [47]:
# рассмотрим общее распределение сообщений в течение срока предоставленных данных

count_of_msg_date = df.groupby('msg_date')[['user']].count()

plt.figure(figsize=(15, 5))
sns.lineplot(x=count_of_msg_date.index, y='user', data=count_of_msg_date, marker='D', color='red')

plt.title('Распределение выхода сообщений в течение срока предоставленных данных')
plt.xlabel('День недели')
plt.ylabel('Количество сообщений')


plt.show()

Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.

Исследование зависимости количества реакций от времени публикации поста¶

In [48]:
# сгруппируем данные по времени выхода поста
mean_of_react_hour = df.groupby('msg_hour')[['count_of_reactions']].mean()

# построим график зависимости количества реакций от времени выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_hour.index, y='count_of_reactions', data=mean_of_react_hour, marker='D', color='green')

plt.title('Среднее количество реакций на сообщения в зависимости от времени выхода поста')
plt.xlabel('Время выхода поста')
plt.ylabel('Среднее количество сообщений')

plt.show()

Больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. И небольшое увеличение активности есть вечером около 17:00 - возможно конец рабочего дня, дорога домой.

In [49]:
# сгруппируем данные по дню недели выхода поста
mean_of_react_weekday = df.groupby('msg_weekday')[['count_of_reactions']].mean()

# построим график зависимости количества реакций от дня недели выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_weekday.index, y='count_of_reactions', data=mean_of_react_weekday, marker='D', color='blue')

plt.title('Среднее количество реакций на сообщения в зависимости от дня недели выхода поста')
plt.xlabel('День недели (0 - понедельник, 6 - воскресенье)')
plt.ylabel('Среднее количество сообщений')

plt.show()

Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов.

In [50]:
# сгруппируем данные по дню месяца выхода поста
mean_of_react_day = df.groupby('msg_day')[['count_of_reactions']].mean()

# построим график зависимости количества реакций от дня месяца выхода поста
plt.figure(figsize=(15, 5))
sns.lineplot(x=mean_of_react_day.index, y='count_of_reactions', data=mean_of_react_day, marker='D', color='orange')

plt.title('Среднее количество реакций на сообщения в зависимости от дня месяца выхода поста')
plt.xlabel('День месяца')
plt.ylabel('Среднее количество сообщений')

plt.show()

Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.

Исследование зависимости скорости ответов на сообщения от времени создания треда¶

У нас имеются данные, когда был создан тред, к которому относится сообщение. Вычислив разницу во времени мы узнаем, через какое время после создания треда было отправлено сообщение, содержащееся в нем.

In [51]:
# вычисляем сколько прошло времени между созданием треда и отправкой сообщения
df['lag'] = df['ts'] - df['thread_ts']
df.head()
Out[51]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments ... cohort group_cohort count_of_reactions msg_hour msg_weekday msg_day msg_month msg_date is_reaction lag
0 ae31e785-257b-4290-a4c6-9721337f67ea U03JYMWQLP5 2022-11-28 13:49:23 2022-11-28 14:24:08 TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 ... 0 data_0 0 13 0 28 11 2022-11-28 пост без реакции 0 days 00:00:00
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 ... 0 data_0 1 14 0 28 11 2022-11-28 пост с реакцией 0 days 00:34:45
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 1 8 1 29 11 2022-11-29 пост с реакцией 0 days 00:00:00
4 b5e3413b-8f04-4192-948b-2423eb3192b2 U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 0 8 1 29 11 2022-11-29 пост без реакции 0 days 00:24:20
7 6a730fa5-7934-41f4-96f0-aa04787bce8e U03DZHHUACW 2022-11-29 10:26:46 2022-11-29 12:08:02 TPV9DP0N4 2022-11-29 10:26:46 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 5 10 1 29 11 2022-11-29 пост с реакцией 0 days 00:00:00

5 rows × 25 columns

In [52]:
# примем, что реакция меньше 30 минут считается быстрой
# выделим сообщения, на которые быстро отреагировали
fast_reaction = df[(df['lag'] > '0 days 00:00:00') & (df['lag'] <= '0 days 00:30:00')]
fast_reaction.head()
Out[52]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments ... cohort group_cohort count_of_reactions msg_hour msg_weekday msg_day msg_month msg_date is_reaction lag
4 b5e3413b-8f04-4192-948b-2423eb3192b2 U040E2D6CF2 2022-11-29 08:32:32 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 0 8 1 29 11 2022-11-29 пост без реакции 0 days 00:24:20
31 9775dcc2-610d-4b3a-ac86-8e721d47f3d1 U02Q4P6SGBC 2022-12-01 13:54:46 NaT TPV9DP0N4 2022-12-01 13:49:57 NaN data_analysts_bus 2022-12-01 0 ... 0 data_0 1 13 3 1 12 2022-12-01 пост с реакцией 0 days 00:04:49
32 33078238-d338-4305-9d45-f600b6883b8f U03JYMWQLP5 2022-12-01 14:17:22 NaT TPV9DP0N4 2022-12-01 13:49:57 NaN data_analysts_bus 2022-12-01 0 ... 0 data_0 1 14 3 1 12 2022-12-01 пост с реакцией 0 days 00:27:25
41 a8938d89-5206-4771-8435-beca0ab21c88 U03JYMWQLP5 2022-12-03 17:07:36 NaT NaN 2022-12-03 16:42:59 NaN data_analysts_bus 2022-12-03 0 ... 0 data_0 1 17 5 3 12 2022-12-03 пост с реакцией 0 days 00:24:37
54 7a121d41-30e0-4fd7-8584-86d7ff4329e9 U03JYMWQLP5 2022-12-07 03:25:24 NaT TPV9DP0N4 2022-12-07 03:16:10 NaN data_analysts_bus 2022-12-06 0 ... 0 data_0 1 3 2 7 12 2022-12-07 пост с реакцией 0 days 00:09:14

5 rows × 25 columns

In [53]:
# рассмотрим зависимость быстроты ответов от времени публикации поста
# сгруппируем данные по времени
fast_react_hour = fast_reaction.groupby('msg_hour')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_hour['total_msg'] = df.groupby('msg_hour')[['user']].count()
# вычислим долю быстрых ответов
fast_react_hour['fast_react_share'] = round(fast_react_hour['user']/fast_react_hour['total_msg']*100,2)
fast_react_hour
Out[53]:
user total_msg fast_react_share
msg_hour
0 3 52 5.77
1 1 46 2.17
3 3 99 3.03
4 1 167 0.60
5 11 282 3.90
6 41 511 8.02
7 117 1148 10.19
8 117 1274 9.18
9 80 1217 6.57
10 123 1216 10.12
11 114 1305 8.74
12 98 1375 7.13
13 184 1465 12.56
14 131 1348 9.72
15 149 1365 10.92
16 57 1145 4.98
17 80 971 8.24
18 67 1073 6.24
19 95 919 10.34
20 38 625 6.08
21 32 367 8.72
22 4 190 2.11
23 3 95 3.16
In [54]:
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_hour.index, y='fast_react_share', data=fast_react_hour, marker='D', color='violet')

plt.title('График зависимости быстрого ответа (меньше 30 минут) от времени создания треда')
plt.xlabel('Время публикации')
plt.ylabel('Процент постов с быстрым ответом')

plt.show()

Больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой.

In [55]:
# рассмотрим зависимость быстроты ответов от дня недели публикации поста
# сгруппируем данные по дню недели
fast_react_weekday = fast_reaction.groupby('msg_weekday')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_weekday['total_msg'] = df.groupby('msg_weekday')[['user']].count()
# вычислим долю быстрых ответов
fast_react_weekday['fast_react_share'] = round(fast_react_weekday['user']/fast_react_weekday['total_msg']*100,2)
fast_react_weekday
Out[55]:
user total_msg fast_react_share
msg_weekday
0 433 3649 11.87
1 276 3165 8.72
2 157 2542 6.18
3 304 3014 10.09
4 184 2565 7.17
5 77 1514 5.09
6 118 1875 6.29
In [56]:
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_weekday.index, y='fast_react_share', data=fast_react_weekday, marker='D', color='violet')

plt.title('График зависимости быстрого ответа (меньше 30 минут) от дня недели создания треда')
plt.xlabel('День недели 0 - понедельник, 6 - воскресенье')
plt.ylabel('Процент постов с быстрым ответом')

plt.show()

Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг.

In [57]:
# рассмотрим зависимость быстроты ответов от дня месяца публикации поста
# сгруппируем данные по дню месяца
fast_react_day = fast_reaction.groupby('msg_day')[['user']].count()
# добавим столбец с общим количеством сообщений из базовой таблицы
fast_react_day['total_msg'] = df.groupby('msg_day')[['user']].count()
# вычислим долю быстрых ответов
fast_react_day['fast_react_share'] = round(fast_react_day['user']/fast_react_day['total_msg']*100,2)
fast_react_day
Out[57]:
user total_msg fast_react_share
msg_day
1 187 1510 12.38
2 107 1386 7.72
3 55 825 6.67
4 47 974 4.83
5 97 1380 7.03
6 68 1324 5.14
7 53 1149 4.61
8 89 1276 6.97
9 66 1098 6.01
10 22 668 3.29
11 55 716 7.68
12 104 859 12.11
13 21 411 5.11
14 15 249 6.02
15 24 218 11.01
21 1 3 33.33
23 1 11 9.09
24 4 6 66.67
25 10 25 40.00
27 16 182 8.79
28 232 1403 16.54
29 187 1418 13.19
30 88 1127 7.81
In [58]:
# визуализируем данные
sns.set_style('whitegrid')
plt.figure(figsize=(15, 5))
sns.lineplot(x=fast_react_day.index, y='fast_react_share', data=fast_react_day, marker='D', color='violet')

plt.title('График зависимости быстрого ответа (меньше 30 минут) от дня месяца создания треда')
plt.xlabel('День месяца')
plt.ylabel('Процент постов с быстрым ответом')

plt.show()

График неравномерный, видно аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.

Исследование зависимости количества реакций от типа группы и когорты¶

In [59]:
# выделим сообщения хотя бы с 1 реакцией в отдельный датасет
is_react = df[df['count_of_reactions'] > 0]
is_react.head()
Out[59]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments ... cohort group_cohort count_of_reactions msg_hour msg_weekday msg_day msg_month msg_date is_reaction lag
1 7f644ae8-16d4-4c9e-9c4f-8b2d6df6a28d U03V483FRKM 2022-11-28 14:24:08 NaT TPV9DP0N4 2022-11-28 13:49:23 NaN data_analysts_bus 2022-11-28 0 ... 0 data_0 1 14 0 28 11 2022-11-28 пост с реакцией 0 days 00:34:45
3 6c5bf2c1-8579-413c-8e8f-ec3a0e3698a8 U03JYMWQLP5 2022-11-29 08:08:12 2022-11-29 10:56:57 NaN 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 1 8 1 29 11 2022-11-29 пост с реакцией 0 days 00:00:00
7 6a730fa5-7934-41f4-96f0-aa04787bce8e U03DZHHUACW 2022-11-29 10:26:46 2022-11-29 12:08:02 TPV9DP0N4 2022-11-29 10:26:46 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 5 10 1 29 11 2022-11-29 пост с реакцией 0 days 00:00:00
9 031b6894-db20-4844-91e4-7c6324351365 U040E2D6CF2 2022-11-29 10:56:57 NaT TPV9DP0N4 2022-11-29 08:08:12 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 1 10 1 29 11 2022-11-29 пост с реакцией 0 days 02:48:45
14 da48fa1a-4632-4aab-a22c-ca0a5ef29d34 U03DZHHUACW 2022-11-29 11:56:44 NaT TPV9DP0N4 2022-11-29 10:26:46 NaN data_analysts_bus 2022-11-29 0 ... 0 data_0 1 11 1 29 11 2022-11-29 пост с реакцией 0 days 01:29:58

5 rows × 25 columns

In [60]:
# сгруппируем данные по типу группы
group_react = is_react.groupby('group_type')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений по группам
group_react['count_of_msg'] = df.groupby('group_type')['user'].count()
# рассчитаем долю сообщений с хотя бы 1 реакцией
group_react['react_share'] = round(group_react['count_of_reactions'] / group_react['count_of_msg']*100,2)
# отсортируем по убыванию доли 
group_react = group_react.sort_values(by='react_share', ascending=False)
group_react
Out[60]:
count_of_reactions count_of_msg react_share
group_type
masterskaya 27 59 45.76
data 86 202 42.57
de 350 1298 26.96
da 1585 6852 23.13
dl 261 1155 22.60
da_plus 308 1396 22.06
ds_plus 284 1306 21.75
ds 1028 5068 20.28
da_bc 84 426 19.72
ds_bc 68 396 17.17
sql 24 166 14.46
In [61]:
# визуализируем данные
fig = px.bar(group_react, x=group_react.index, y='react_share', color=group_react.index,\
            title='Доля сообщений с реакциями с разбивкой по типам групп',\
            color_discrete_sequence=px.colors.qualitative.Set2,\
            text='react_share')

fig.update_layout(showlegend=False,
                 xaxis_title='Тип группы',
                 yaxis_title='Процент сообщений')
fig.show()

Самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Чтобы учесть общее количество сообщений рассмотрим также среднее количество реакций на каждый пост по типу группы.

In [62]:
# визуализируем среднее количество реакций на каждый пост по типу группы
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
    ax = sns.barplot(data=df, x='count_of_reactions', y='group_type', orient='h',\
                    order=df.groupby('group_type')['count_of_reactions'].mean().\
                    sort_values(ascending=False).index)
    ax.set_title('Среднее количество реакций на 1 пост по группам')
    ax.set_xlabel('Количество реакций на 1 сообщение')
    ax.set_ylabel('Тип группы')
    sns.despine()

Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.

Исследование зависимости количества реакций от типа канала¶

In [63]:
# сгруппируем данные по типу канала
channel_react = is_react.groupby('channel_type')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений по каналам
channel_react['count_of_msg'] = df.groupby('channel_type')['user'].count()
# рассчитаем долю сообщений с хотя бы 1 реакцией
channel_react['react_share'] = round(channel_react['count_of_reactions'] / channel_react['count_of_msg']*100,2)
# отсортируем по убыванию доли 
channel_react = channel_react.sort_values(by='react_share', ascending=False)
channel_react
Out[63]:
count_of_reactions count_of_msg react_share
channel_type
library 25 39 64.10
masterskaya 27 59 45.76
info 878 2823 31.10
other 592 2409 24.57
teamwork 733 3346 21.91
exerciser 871 4036 21.58
projects 979 5612 17.44
In [64]:
# визуализируем данные
fig = px.bar(channel_react, x=channel_react.index, y='react_share', color=channel_react.index,\
            title='Доля сообщений с реакциями с разбивкой по типам каналов',\
            color_discrete_sequence=px.colors.qualitative.Set2,\
            text='react_share')

fig.update_layout(showlegend=False,
                 xaxis_title='Тип канала',
                 yaxis_title='Процент сообщений')

fig.show()

Процент реакций в канале library - 64% - очень хороший. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию.

In [65]:
# визуализируем среднее количество реакций на каждый пост по типу канала
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
    ax = sns.barplot(data=df, x='count_of_reactions', y='channel_type', orient='h',\
                    order=df.groupby('channel_type')['count_of_reactions'].mean().\
                    sort_values(ascending=False).index)
    ax.set_title('Среднее количество реакций на 1 пост по каналам')
    ax.set_xlabel('Количество реакций на 1 сообщение')
    ax.set_ylabel('Тип канала')
    sns.despine()

Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост.

В целом, судя по таблице channel_react, можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.

Исследование количества реакиций в зависимости от длины сообщения¶

In [66]:
plt.figure(figsize=(15, 5))
sns.set_style('whitegrid',
              {'axes.facecolor': '0.8',
               'grid.color': '0.2',
               'figure.facecolor': '0.7'})
sns.scatterplot(x='text_len', y='count_of_reactions', data=is_react)

plt.title('График зависимости количества реакций от количества символов')
plt.xlabel('Количество символов в сообщении')
plt.ylabel('Количество реакций')

plt.show()
In [67]:
plt.figure(figsize=(15, 5))
sns.set_style('whitegrid',
              {'axes.facecolor': '0.8',
               'grid.color': '0.2',
               'figure.facecolor': '0.7'})
sns.scatterplot(x='text_words', y='count_of_reactions', data=is_react)

plt.title('График зависимости количества реакций от количества слов')
plt.xlabel('Количество слов в сообщении')
plt.ylabel('Количество реакций')

plt.show()

В целом можно сказать, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.

Исследование количества реакций в канале info¶

Выявленная проблема от заказчика - отсутствия реакций на важные анонсы. Анонсы чаще всего делают кураторы и наставники. Канал info - основной канал общения кураторов со студентами. Кроме того, по правилам этого канала сообщения в него могут писать только куратор и наставник, студенты должны отвечать в тредах. Проанализируем отдельно активность в этом канале.

Мы знаем, что в таблице в этом канале преедставлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2.

In [68]:
# выделим в отдельную таблицу сообщения канала info
info = df[df['channel_type'] == 'info']

# выделим сообщения, не относящиеся к треду
# это и будут предположительно сообщения от кураторов и наставников
t_info = info[info['thread_ts'].isnull()]
t_info.head()
Out[68]:
client_msg_id user ts latest_reply team thread_ts subtype channel file_date attachments ... cohort group_cohort count_of_reactions msg_hour msg_weekday msg_day msg_month msg_date is_reaction lag
170 a5c6ff4f-f4e6-4d97-8e9a-a2675b68f5b7 U0104BRNQG1 2022-11-28 09:30:14 NaT TPV9DP0N4 NaT NaN data_edteam_info 2022-11-28 0 ... 0 data_0 6 9 0 28 11 2022-11-28 пост с реакцией NaT
171 5657aaec-25c7-44e1-b2ac-ff0867f81149 U01DVNLSSBZ 2022-11-28 15:28:09 NaT TPV9DP0N4 NaT NaN data_edteam_info 2022-11-28 0 ... 0 data_0 2 15 0 28 11 2022-11-28 пост с реакцией NaT
172 2D934809-3B8D-4BAC-A756-BED3DD40C4B3 UTZ0ZG8TB 2022-11-30 12:40:02 NaT TPV9DP0N4 NaT NaN data_edteam_info 2022-11-30 0 ... 0 data_0 0 12 2 30 11 2022-11-30 пост без реакции NaT
173 5e14f061-fed4-46e0-bd01-cff41c271fc0 U01733EE0G7 2022-11-30 13:18:14 NaT TPV9DP0N4 NaT NaN data_edteam_info 2022-11-30 0 ... 0 data_0 0 13 2 30 11 2022-11-30 пост без реакции NaT
174 c2fa117f-499b-4d75-863c-ee23a5fc1a43 U01DVNLSSBZ 2022-12-01 08:52:18 NaT TPV9DP0N4 NaT NaN data_edteam_info 2022-12-01 0 ... 0 data_0 0 8 3 1 12 2022-12-01 пост без реакции NaT

5 rows × 25 columns

In [69]:
# проведем группировку для графика
t_info_react = t_info.groupby('is_reaction')[['user']].count()
t_info_react
Out[69]:
user
is_reaction
пост без реакции 314
пост с реакцией 189
In [70]:
# круговая диаграмма доли постов с наличием хотя бы  реакции
fig = px.pie(t_info_react, values='user', names=t_info_react.index, color_discrete_sequence=px.colors.sequential.Burgyl, \
             title='Доля постов с реакциями в канале info')
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.update_layout(showlegend=False)
fig.show()

Количество постов с реакциями около 40% - для анонсов не самый ожидаемый отклик.

In [71]:
# рассмотрим распределение и выбросы в столбце text_len

plt.figure(figsize=(15, 5))
with sns.color_palette('Set2'):
    sns.boxplot(data=t_info, x='count_of_reactions');

Большинство постов имеет до 2 реакций. Посты с больше чем 5 реакциями уже отмечены как выбросы.

In [72]:
# визуализируем среднее количество реакций на каждый пост по типу группы
plt.figure(figsize=(12, 5))
with sns.color_palette('Set2'):
    ax = sns.barplot(data=t_info, x='count_of_reactions', y='group_type', orient='h',\
                    order=t_info.groupby('group_type')['count_of_reactions'].mean().\
                    sort_values(ascending=False).index)
    ax.set_title('Среднее количество реакций на 1 пост по группам в канале info')
    ax.set_xlabel('Количество реакций на 1 сообщение')
    ax.set_ylabel('Тип группы')
    sns.despine()

Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост.

In [73]:
# рассмотрим количество реакций по когортам
# выделим сообщения с хотя бы 1 реакцией и сгруппируем по когортам
info_gr = info[info['count_of_reactions']>0].groupby('group_cohort')[['count_of_reactions']].count()
# добавим столбец с общим количеством сообщений в канале info 
info_gr['msg_cnt'] = info.groupby('group_cohort')[['user']].count()
# рассчитаем долю сообщений с реакциями
info_gr['react_share'] = round(info_gr['count_of_reactions']/ info_gr['msg_cnt']*100,2)
# отсортируем результат
info_gr = info_gr.sort_values(by='react_share', ascending=False)
# выделим первые 15 когорт по доли реакции
info_gr_head = info_gr.head(15)
info_gr_head
Out[73]:
count_of_reactions msg_cnt react_share
group_cohort
ds_40 1 1 100.00
de_25 1 1 100.00
da_bc_04 10 12 83.33
da_plus_18 18 24 75.00
da_bc_02 7 10 70.00
ds_plus_17 17 27 62.96
ds_57 20 36 55.56
ds_44 15 28 53.57
ds_plus_08 11 21 52.38
ds_plus_12 13 25 52.00
data_0 19 37 51.35
de_7 1 2 50.00
ds_plus_15 27 55 49.09
ds_50 25 52 48.08
da_61 50 108 46.30
In [74]:
# визуализируем результат
fig = px.bar(info_gr_head, x=info_gr_head.index, y='react_share', color=info_gr_head.index,\
            title='Доля сообщений с реакциями в канале инфо по когортам',\
            color_discrete_sequence=px.colors.qualitative.Set2,\
            text='react_share')

fig.update_layout(showlegend=False,
                 xaxis_title='Тип группы и когорта',
                 yaxis_title='Процент сообщений')
fig.show()

Если не рассматривать когорты с 1 сообщением в канале, то активность в некоторых доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных.

In [75]:
# выделим последние 15 когорт по доли реакции
info_gr_tail = info_gr.tail(15)
info_gr_tail
Out[75]:
count_of_reactions msg_cnt react_share
group_cohort
ds_plus_06 2 11 18.18
ds_51b 1 6 16.67
da_53 7 45 15.56
de_22 2 14 14.29
da_56b 2 16 12.50
da_50 2 20 10.00
ds_46 5 53 9.43
da_plus_11 1 12 8.33
da_61b 3 39 7.69
ds_plus_05 1 15 6.67
ds_plus_09 1 15 6.67
da_plus_13 1 16 6.25
ds_bc_04 2 32 6.25
ds_bc_02 1 16 6.25
da_plus_09 1 26 3.85
In [76]:
# визуализируем результат
fig = px.bar(info_gr_tail, x=info_gr_tail.index, y='react_share', color=info_gr_tail.index,\
            title='Доля сообщений с реакциями в канале инфо по когортам',\
            color_discrete_sequence=px.colors.qualitative.Set2,\
            text='react_share')

fig.update_layout(showlegend=False,
                 xaxis_title='Тип группы и когорта',
                 yaxis_title='Процент сообщений')
fig.show()

В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.

Общией анализ сообщений в датасете показал, что больше всего сообщений генерируют группы da - их больше всего когорт. С разбивкой по каналам больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и, как следствие, большим количеством вопросов.

Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.

Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.

Только 22,4% постов получают хотя бы одну реакцию, что довольно немного.

При анализе времени публикации сообщений выявлено, что самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше. Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу. Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.

Исследование реакций показало, что больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов. Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.

Исследование скорости реакции на сообщения исходя из ответов на созданные треды показало, что больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой. Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг. Исследование дней месяца показало аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.

Исследование групп выявило, что самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.

Исследование каналов показало самый высокий процент реакций в канале library - 64%. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию. Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост. Можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.

Исследование длины сообщений показало, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.

Отдельно был проанализирован канал info. В этом канале представлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2. Количество постов с реакциями около 40% - для анонсов не самый хороший отклик. Большинство постов имеет до 2 реакций. Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост. Активность в некоторых когортах доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных. В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.

Общий вывод¶

Для анализа был предоставлен датасет с историей сообщений пользователей с 21.10.22 по 25.12.22 гг.

В результате предобработки мы удалили не несущий информацию столбец type. Выяснили, что большинство пропущенных значений client_id_msg - это сообщения со служебными метками, а также что сообщения от бота не имеют user id. Пропуски в столбцах subtype, client_msg_id, latest_reply, tread_ts, team, user оставили как есть. Удалили явные дубликаты, а также выяснили, что большинство сообщений находятся в пределах 55 слов и 400 символов.

Для проведения анализа в таблицу были добавлены следующие столбцы: channel_type - тип канала, group_type - тип группы, cohort - когорта, group_cohort - группа и когорта, count_of_reactions - количество реакций на пост, msg_hour - время сообщения, msg_weekday - день недели сообщения, msg_day - день месяца сообщения, msg_month - месяц сообщения, msg_date - дату сообщения. Также из таблицы были удалены служебные сообщения, не несущие информации для анализа.

Общий анализ сообщений в датасете показал, что больше всего сообщений генерируют группы da - их больше всего когорт. С разбивкой по каналам больше всего сообщений в канале projects, что объясняется самостоятельной работой над проектами и, как следствие, большим количеством вопросов.

Cамые длинные сообщения встречаются в канале library - в нем публикуются дополнительные тематическе материаламы: статьи, ссылки на учебники и полезные книги. Далее каналы projects и exerciser, где публикуются вопросы и объяснения к ним. Каналы info, teamwork и other имеют примерно одинаковое медианное количество символов в сообщениях.

Самые многословные группы - это студенты дата аналитик буткемп - короткосрочной программы анализа данных. Далее идут студенты расширенных групп (plus). Самые короткие сообщения пишут в канале мастерской.

Только 22,4% постов получают хотя бы одну реакцию, что довольно немного.

При анализе времени публикации сообщений выявлено, что самое большое количество сообщений выходит в 13:00 и в 15:00. В остальном в течение дня выход сообщений распределен более менее равномерно с 7:00 до 20:00, но к концу дня публикаций меньше. Больше всего сообщений генерируется в понедельник. Второй пик - в четверг, перед концом недели. Меньше всего в субботу. Основное количество сообщений опубликовано с 28 ноября 17 декабря 2022 - это примерно 3 недели. Возможно время активности обусловлено проведением спринта в большинстве когорт, представленных для анализа.

Исследование реакций показало, что больше всего реакций получают посты, выходящие в 7:00. Это время подъема или выхода на работу большинства людей. Второй пик - 13:00 - время обеда - но тут среднее количество реакций существенно ниже. Большее количество реакций получают посты, выходящие в понедельник - люди настроены на новую неделю после выходных и еще не загружены работой. Меньше всего реакций в воскресенье - выходной, большинству не до учебных чатов. Наибольшая активность наблюдается в середине и конце месяца, в первых числах выходило много постов (так как основной период анализа 3 недели - с 28 ноября по 17 декабря, предположительно недели спринта) и результат расчета среднего падает.

Исследование скорости реакции на сообщения исходя из ответов на созданные треды показало, что больше всего быстрых ответов получают сообщения, опубликованные в 13:00 и 15:00 - время начала и конца обеда у большинства людей. Также достаточно высокую активность имеют сообщения, вышедшие в 7:00, 10:00, 14:00, 19:00 - время прихода на работу, обеда и возвращения домой. Большее количество быстрых ответов получают посты, опубликованные в понедельник. Вторые по откликам - посты, опубликованные в четверг. Исследование дней месяца показало аномально высокую активность с середины месяца до 26 числа. Это связано с маленькой выборкой данных за эти даты, так как большинство сообщений было опубликовано с 28 ноября по 17 декабря. В эти даты пики это 28 ноября - судя по всему дата начала спринта (понедельник), 1 декабря, 12 декабря - то есть в конце месяца, в начале и в середине.

Исследование групп выявило, что самые часто реагируемые сообщения в группах masterskaya и data. Самые мало реагируемые сообщения в группах sql. В остальных группах процент реакций находится примерно на уровне 20%. Среднее количество реакций на пост равно 1 в группах data. В самых многочисленных по количеству сообщений группах da и ds среднее количество реакций на пост равно 0,65 и 0,42 соответственно. Как и в предущих расчетах меньше всего реакций в группах sql.

Исследование каналов показало самый высокий процент реакций в канале library - 64%. По всей видимости структурированная дополнительная информация воспринимается хорошо и имеет большой отклик. Сообщения в канале masterskaya также имеют достаточно высокий отклик - почти 46% сообщений имеют хотя бы 1 реакцию. Меньше всего реакций в канале projects - туда заходят задать вопрос или найти ответ, редко у кого возникает желание поставить реакцию. Сообщения в канале library имеют в среднем больше 4 реакций на пост - это очень хороший показатель. Сообщения в канале info - около 2 реакций на пост. Учитывая, что в этом канале чаще всего публикуются анонсы мероприятий, это довольно мало. Остальные каналы имеют меньше 1 реакции на пост. Можно предположить, что чем больше сообщений в группе, тем меньше процент отклика.

Исследование длины сообщений показало, что чем меньше сообщение по длине, тем более вероятно, что его дочитают до конца и что у него будут реакции. Но и коротких сообщений с нулевым реагированием достаточно.

Отдельно был проанализирован канал info. В этом канале представлено 2823 сообщения, медиана сообщения около 100 символов. Около 25% сообщений в этом канале имеют хотя бы 1 реакцию и среднее количество реакций на один пост около 2. Количество постов с реакциями около 40% - для анонсов не самый хороший отклик. Большинство постов имеет до 2 реакций. Самые активные группы - da_bc и ds - больше 4 реакций в среднем на один пост куратора. Самая малоактивная группа - sql - меньше 1 реакции на пост. Активность в некоторых когортах доходит до 80%, что обнадеживает - значит в других когортах можно достичь подобного результата, если подробнее проанализировать сообщения. К сожалению, а данном датасете для этого недостаточно данных. В отдельных когортах доля реакций не превышает 10%, что тоже требует дополнительного анализа.

Рекомендации¶

  • В начало каждого сообщения с анонсом добавлять призыв оставить реакцию. До конца сообщения дочитываются не всегда.
  • Публиковать важные анонсы в наиболее благоприятные часы: 07:00, 10:00, 13:00-15:00 и вечернее время 19:00. Предпочтительные дни недели - понедельник и четверг
  • Дополнительно проанализировать сообщения в активных когортах da_bc_04, da_plus_18, da_bc_02, da_plus_17, а также в наименее активных когортах da_plus_09, ds_bc_02, для выявления возможных причин активности/неактивности.
  • Публиковать меньше сообщений общего содержания в каналах info, чтобы не отвлекать людей от работы или учебы.
In [ ]: